package de.sendorian.app.forumArchive.download;


import static de.sendorian.app.forumArchive.renamer.Renamers.*;
import static org.apache.commons.lang.StringUtils.*;
import static org.hamcrest.Matchers.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.log4j.Logger;
import org.hamcrest.Matcher;

import de.sendorian.app.forumArchive.Config;
import de.sendorian.app.forumArchive.domain.Archive;
import de.sendorian.app.forumArchive.domain.Forum;
import de.sendorian.app.forumArchive.domain.Image;
import de.sendorian.app.forumArchive.domain.Post;
import de.sendorian.app.forumArchive.domain.Topic;
import de.sendorian.app.forumArchive.renamer.FileRenamer;
import de.sendorian.util.io.FileWriter;

public class Downloader {

	private static final Logger logger = Logger.getLogger(Downloader.class.getName());

	FileRenamer renamer = nothing();

	Matcher<Post> postMatcher = anything();

	Matcher<Topic> topicMatcher = anything();

	private int threads;

	private static ExecutorService threadPool;


	public Downloader(FileRenamer renamer) {
		this.renamer = renamer;
		threads = Config.get().getDownloadThreadCount();
		logger.info("Downloader uses " + threads + " threads to download files.");
	}


	public Downloader(Matcher<Post> postMatcher, FileRenamer renamer) {
		this(renamer);
		this.postMatcher = postMatcher;
	}


	public Downloader(Matcher<Post> postMatcher, Matcher<Topic> topicMatcher, FileRenamer renamer) {
		this(renamer);
		this.postMatcher = postMatcher;
		this.topicMatcher = topicMatcher;
	}


	public void downloadForums(List<Forum> forums) {
		for (Forum forum : forums) {
			downloadForum(forum);
		}
	}


	public void downloadForum(Forum forum) {
		threadPool = Executors.newFixedThreadPool(threads);

		List<TopicInfos> topicInfos = new ArrayList<TopicInfos>();
		for (Topic topic : forum.getTopics()) {
			if (topicMatcher.matches(topic)) {
				topicInfos.add(downloadPosts(topic, forum.getName()));
			}
		}

		waitForDownloadsToComplete();

		Set<String> externalLinks = new TreeSet<String>();
		String downloadedFilesInfo = "";
		for (TopicInfos topicInfo : topicInfos) {
			if (!topicInfo.getDownloadedFiles().isEmpty()) {
				saveTextFile(topicInfo.getTopic().toStringFormatted(), topicInfo.getPath(),
				    "Posts.txt");
				downloadedFilesInfo += topicInfo.getDownloadedFilesString();
			}
			externalLinks.addAll(topicInfo.getExternalLinks());
		}

		String forumDir = Config.get().getDownloadDir() + forum.getName();
		String dateString = DateFormatUtils.format(new Date(), "yyyy_MM_dd");
		if (!isEmpty(downloadedFilesInfo)) {
			saveTextFile(downloadedFilesInfo, forumDir, "downloaded files " + dateString + ".txt");
		}

		String externalLinksString = getExternalLinksString(externalLinks);
		if (!isEmpty(externalLinksString)) {
			saveTextFile(externalLinksString, forumDir, "external links " + dateString + ".txt");
		}
	}


	/**
	 * Downloads all Posts of the given topic that match the Downloaders post matcher.
	 */
	private TopicInfos downloadPosts(Topic topic, String forumName) {
		String forumPath = Config.get().getDownloadDir() + forumName;
		String path = forumPath + java.io.File.separator + topic.getName();
		Set<String> externalLinks = new TreeSet<String>();
		Set<String> downloadedFiles = new TreeSet<String>();

		for (Post post : topic.getPosts()) {
			if (!postMatcher.matches(post)) {
				continue;
			}

			List<String> filesInArchives = new ArrayList<String>();
			if (!post.getArchives().isEmpty()) {
				for (Archive archive : post.getArchives()) {
					archive.setLocalPath(path);
					ArchiveDownloaderTask archiveDownloader = new ArchiveDownloaderTask(archive,
					    topic, downloadedFiles, filesInArchives);
					threadPool.execute(archiveDownloader);
				}
			}
			if (!post.getImages().isEmpty()) {
				for (Image image : post.getImages()) {
					renamer.rename(image);
					image.setLocalPath(path);
					ImageDownloaderTask imageDownloader = new ImageDownloaderTask(image, topic,
					    downloadedFiles, filesInArchives);
					threadPool.execute(imageDownloader);
				}
			}

			externalLinks.addAll(post.getExternalLinks());
		}

		return new TopicInfos(topic, path, downloadedFiles, externalLinks);
	}


	/**
	 * Waits until all download tasks have been executed.
	 */
	private void waitForDownloadsToComplete() {
		logger.info("All download tasks have been initiated. Waiting for tasks to complete.");
		threadPool.shutdown();
		try {
			threadPool.awaitTermination(90 * 60, TimeUnit.SECONDS);
			logger.info("All download tasks have been completed.");
		} catch (InterruptedException e) {
			logger.error("Downloader was interrupted!", e);
		}
	}


	public String getExternalLinksString(Set<String> externalLinks) {
		return createTopicInfoList(externalLinks, null);
	}


	static String createTopicInfoList(Set<String> infosToSave, String title) {
		if (infosToSave.isEmpty()) {
			return "";
		}

		StringBuilder builder = new StringBuilder();
		if (title != null) {
			builder.append(title + "\n");
		}
		for (String externalLink : infosToSave) {
			builder.append(externalLink).append('\n');
		}
		builder.append("\n\n");
		return builder.toString();
	}


	private static void saveTextFile(String fileContent, String forumPath, String fileName) {
		try {
			FileWriter fileWriter = new FileWriter();
			fileWriter.deleteFile(forumPath, fileName);
			fileWriter.write(fileContent.getBytes(), forumPath, fileName);
		} catch (IOException e) {
			logger.error("Could not write forum info file \"" + fileName + "\"", e);
		}
	}
}
